package org.urbansoft.icl.field;

import java.io.PrintStream;

import org.apache.log4j.Logger;
import org.urbansoft.iso.ISOException;
import org.urbansoft.iso.Padder;
import org.urbansoft.icl.CashLetter;
import org.urbansoft.icl.field.type.FieldType;
import org.urbansoft.icl.field.validation.ValidationResult;
import org.urbansoft.icl.field.validation.Validator;
import org.urbansoft.icl.util.ICLException;

/**
 * The Class Field.
 */
public abstract class Field {
	private static Logger log = Logger.getLogger(CashLetter.class);
	protected String fieldName;
	protected String validationCriteria;
	protected String usage;
	protected Position position;
	protected FieldType fieldType;
	protected String fieldData;
	protected byte[] fieldDataBytes;
	protected Padder padder;
	protected Validator fieldValidator;
	protected boolean validated = false;

	/**
	 * Initialize.
	 */
	protected abstract void initialize();

	/**
	 * Gets the field name.
	 * 
	 * @return the field name
	 */
	public String getFieldName() {
		return fieldName;
	}

	/**
	 * Sets the field name.
	 * 
	 * @param fieldName
	 *            the new field name
	 */
	public void setFieldName(String fieldName) {
		this.fieldName = fieldName;
	}

	/**
	 * Gets the validation criteria.
	 * 
	 * @return the validation criteria
	 */
	public String getValidationCriteria() {
		return validationCriteria;
	}

	/**
	 * Sets the validation criteria.
	 * 
	 * @param validationCriteria
	 *            the new validation criteria
	 */
	public void setValidationCriteria(String validationCriteria) {
		this.validationCriteria = validationCriteria;
	}

	/**
	 * Gets the usage.
	 * 
	 * @return the usage
	 */
	public String getUsage() {
		return usage;
	}

	/**
	 * Sets the usage.
	 * 
	 * @param usage
	 *            the new usage
	 */
	public void setUsage(String usage) {
		this.usage = usage;
	}

	/**
	 * Gets the position.
	 * 
	 * @return the position
	 */
	public Position getPosition() {
		return position;
	}

	/**
	 * Sets the position.
	 * 
	 * @param position
	 *            the new position
	 */
	public void setPosition(Position position) {
		this.position = position;
	}

	/**
	 * Gets the field type.
	 * 
	 * @return the field type
	 */
	public FieldType getFieldType() {
		return fieldType;
	}

	/**
	 * Sets the field type.
	 * 
	 * @param fieldType
	 *            the new field type
	 */
	public void setFieldType(FieldType fieldType) {
		this.fieldType = fieldType;
	}

	/**
	 * Gets the field data.
	 * 
	 * @return the field data
	 */
	public String getFieldData() {
		return fieldData;
	}

	/**
	 * Gets the padder.
	 * 
	 * @return the padder
	 */
	public Padder getPadder() {
		return padder;
	}

	/**
	 * Sets the padder.
	 * 
	 * @param padder
	 *            the new padder
	 */
	public void setPadder(Padder padder) {
		this.padder = padder;
	}

	/**
	 * Gets the field validator.
	 * 
	 * @return the field validator
	 */
	public Validator getFieldValidator() {
		return fieldValidator;
	}

	/**
	 * Sets the field validator.
	 * 
	 * @param fieldValidator
	 *            the new field validator
	 */
	public void setFieldValidator(Validator fieldValidator) {
		this.fieldValidator = fieldValidator;
	}

	/**
	 * Checks if is validated.
	 * 
	 * @return true, if is validated
	 */
	public boolean isValidated() {
		return validated;
	}

	/**
	 * Gets the field data bytes.
	 * 
	 * @return the field data bytes
	 */
	public byte[] getFieldDataBytes() {
		if (fieldDataBytes != null) {
			return fieldDataBytes;
		}
		return fieldData.getBytes();
	}

	/**
	 * Sets the field data bytes.
	 * 
	 * @param b
	 *            the new field data bytes
	 */
	public void setFieldDataBytes(byte[] b) {
		this.fieldDataBytes = b;
	}

	/**
	 * Sets the field data.
	 * 
	 * @param fieldData
	 *            the new field data
	 * 
	 * @return true, if sets the field data
	 * 
	 * @throws ICLException
	 *             the ICL exception
	 */
	public boolean setFieldData(String fieldData) throws ICLException {
		try {
			// log.info("Actualizando");
			/* as a base measure make sure the data isn't longer than the field */
			if (validLength(fieldData)) {
				// log.info("Largo OK");
				/*
				 * do we require validation separate from the generic type
				 * validation?
				 */
				if (validationCriteria.equals(ValidationCriteria.REQUIRED)
						|| (!fieldData.trim().equals("") && validationCriteria
								.equals(ValidationCriteria.REQUIRED_IF_PRESENT))) {
					// log.info("Con validacion de requerido");
					if (validateData(fieldData)) {
						// log.info("Con data OK");
						/* set the field */
						commitFieldData(fieldData);
					} else {
						throw new ICLException("Field Data for field: "
								+ fieldName + " was is not valid for type: "
								+ fieldType.getName() + " data passed: ["
								+ fieldData + "]");
					}
				} else {
					/*
					 * no data validation needed for this field, just validate
					 * against type
					 */
					if (fieldType.isDataValid(fieldData)) {
						// log.info("Sin Validacion de Requerido, pero si de data");
						commitFieldData(fieldData);
					} else {
						throw new ICLException("Field data for field: "
								+ fieldName
								+ " was not valid for the field type: "
								+ fieldType.getName() + " data passed: ["
								+ fieldData + "]");
					}
				}
			} else {
				throw new ICLException("Length of Field Data for field: "
						+ fieldName + " is too long for type: "
						+ fieldType.getName() + " data passed: [" + fieldData
						+ "]");
			}
		} catch (Exception e) {
			String newLine = System.getProperty("line.separator");
			log.error("Exception Caught: ", e);
			throw new ICLException("Error setting: " + fieldName + newLine
					+ e.getMessage() + " data passed: [" + fieldData + "]");
		}
		return true;
	}

	/**
	 * Commit field data.
	 * 
	 * @param fieldData
	 *            the field data
	 * 
	 * @throws ICLException
	 *             the ICL exception
	 */
	private void commitFieldData(String fieldData) throws ICLException {
		this.validated = true;
		try {
			this.fieldData = padder.pad(fieldData, position.getLength());
			// log.info("Campo Actualizado");
		} catch (ISOException e) {
			log.error("Exception Caught: ", e);
			throw new ICLException("Exception Caught", e);
		}
	}

	/**
	 * Validate data.
	 * 
	 * @param fieldData
	 *            the field data
	 * 
	 * @return true, if successful
	 * 
	 * @throws ICLException
	 *             the ICL exception
	 */
	private boolean validateData(String fieldData) throws ICLException {
		if (fieldValidator != null) {
			ValidationResult v = fieldValidator.validateData(fieldData);
			if (!v.isDataValid()) {
				throw new ICLException("Field Validation Failed: "
						+ v.getMessage());
			}
		}
		// finally make sure it meets the packager's requirements whether or not
		// the user set up a field validator
		return fieldType.isDataValid(fieldData);
	}

	/**
	 * Valid length.
	 * 
	 * @param fieldData
	 *            the field data
	 * 
	 * @return true, if successful
	 */
	private boolean validLength(String fieldData) {
		// log.info("Data length:"+fieldData.length()+", position:"+position.getLength());
		if (fieldData.length() > position.getLength()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.urbansoft.icl.util.Loggeable#dump(java.io.PrintStream,
	 * java.lang.String)
	 */
	public void dump(PrintStream p, String indent) {
		p.println("<field name=\"" + fieldName + "\" usage=\"" + usage
				+ "\" position=\"" + position.toString() + "\"" + " type=\""
				+ fieldType.getShortName() + "\" validationCriteria=\""
				+ validationCriteria + "\" />");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		String fieldClass = getClass().getSimpleName();
		return "<field name=\"" + fieldName + "\" fieldData=\"" + fieldData
				+ "\" position=\"" + position.toString() + "\" />";
	}

}
